# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: neo

import os
import datetime

from absl.flags import FLAGS, DEFINE_string

DEFINE_string('daily_feed_root', '', '/remote/iosg/data/repository/feed/daily')

# daily feed directory structure:
# <daily feed root> / <symbol set> / <date> / <data set> / <table>
#
# daily feed file name structure:
# <item>.<sub_item>[--key1-value1--key2-value2...][.ext]
#
# Example Path
# <daily feed root> / <symbol set> / <date> / presto / index / date.value
# <daily feed root> / <symbol set> / <date> / presto / index / symbol.value
# <daily feed root> / <symbol set> / <date> / presto / product_info / xxx.xxx
# <daily feed root> / <symbol set> / <date> / presto / universe / top500.value
# <daily feed root> / <symbol set> / <date> / presto / availability / xxx.xxx
# <daily feed root> / <symbol set> / <date> / presto / ban / nomura.value
#
# Example of <date>
# - YYYYMMDD
# - main  // meaining symbolic link to the most recent one.
#
# Example of <symbol set>
# - jp
# - kr
# - asia
#
# Example of <data set>
# - factset
# - ibes
# - presto
#
# Example of <table>
# - ff_basic_r_af_v2
# - fe_basic_conh_af
# - product_info


class RepositoryLocationResolver(object):
  keywords = set([
      "daily_feed_root",  # optional [default = FLAGS.daily_feed_root]
      "symbol_set",
      "date",
      "data_set",
      "table",
      "item",
      "sub_item",
      "extension"  # optional [default = ""]. It deos not include dot.
  ])

  @staticmethod
  def _to_string(value):
    if isinstance(value, str):
      return value
    elif isinstance(value, unicode):
      return value
    elif isinstance(value, datetime.date):
      return value.strftime("%Y%m%d")
    else:
      raise Exception("Unknown type of date:%s" % str(type(value)))

  @staticmethod
  def _try_to_convert_to_date(date):
    try:
      if len(date) == 8:
        year = int(date[0:4])
        month = int(date[4:6])
        day = int(date[6:8])
        return datetime.date(year, month, day)
      else:
        return date
    except Exception:
      return date

  @staticmethod
  def _construct_filename(item, sub_item, param, extension):
    filename = "%s.%s" % (item, sub_item)
    for key, value in sorted(param.items()):
      filename += "--%s-%s" % (key, value)
    if extension:
      filename += ".%s" % extension
    return filename

  @staticmethod
  def _construct_short_filename(item, param, extension):
    filename = "%s" % item
    for key, value in sorted(param.items()):
      filename += "--%s-%s" % (key, value)
    if extension:
      filename += ".%s" % extension
    return filename

  @classmethod
  def _check_str(cls, str_value):
    if "." in cls._to_string(str_value):
      return False
    if "-" in cls._to_string(str_value):
      return False
    return True

  @classmethod
  def _check_dict(cls, a_dict):
    for key, value in a_dict.items():
      if key == "daily_feed_root":
        continue
      if (not cls._check_str(value)) or (not cls._check_str(key)):
        return False
    return True

  @classmethod
  def _exclude_keywords(cls, input_param):
    return {key: value for key, value in input_param.items() if key not in cls.keywords}

  @staticmethod
  def get_filename(*args, **kwargs):
    return RepositoryLocationResolver._construct_filename(*args, **kwargs)

  @staticmethod
  def get_short_filename(*args, **kwargs):
    return RepositoryLocationResolver._construct_short_filename(*args, **kwargs)

  @classmethod
  def resolve_daily_feed_file(cls, param):
    # If the sanity check is too strict, we can make it less strict by checking
    # after removing cls.keywords.
    if not cls._check_dict(param):
      raise Exception("Wrong filename:%s" % str(param))

    extension = ""
    if "extension" in param:
      extension = param["extension"]
    if not cls._check_str(extension):
      raise Exception("wrong extension")

    if not "item" in param:
      raise Exception("No item in obj")
    if not "sub_item" in param:
      raise Exception("No sub_item in obj")
    if not cls._check_str(param["item"]):
      raise Exception("wrong item")
    if not cls._check_str(param["sub_item"]):
      raise Exception("wrong sub_item")
    item = param["item"]
    sub_item = param["sub_item"]
    excluded_param = cls._exclude_keywords(param)
    filename = cls._construct_filename(item, sub_item, excluded_param, extension)
    dirname = cls.resolve_daily_feed_dir(param)
    return os.path.join(dirname, filename)

  @classmethod
  def resolve_daily_feed_dir(cls, param):
    # If the sanity check is too strict, we can make it less strict by checking
    # after removing cls.keywords.
    if not cls._check_dict(param):
      raise Exception("Wrong filename:%s" % str(param))

    if "daily_feed_root" in param:
      daily_feed_root = param["daily_feed_root"]
    else:
      daily_feed_root = FLAGS.daily_feed_root

    # if not "symbol_set" in param:
    #  raise Exception("No symbol_set in obj")
    if not "date" in param:
      raise Exception("No date in obj")
    if not "data_set" in param:
      raise Exception("No data_set in obj")
    if not "table" in param:
      raise Exception("No table in obj")
    # if not cls._check_str(param["symbol_set"]):
    #  raise Exception("wrong symbol_set")
    if not cls._check_str(param["date"]):
      raise Exception("wrong date")
    if not cls._check_str(param["data_set"]):
      raise Exception("wrong data_set")
    if not cls._check_str(param["table"]):
      raise Exception("wrong table")

    # symbol_set = param["symbol_set"]
    date_str = cls._to_string(param["date"])
    data_set = param["data_set"]
    table = param["table"]
    dirname = os.path.join(daily_feed_root, date_str, data_set, table)
    return dirname

  @classmethod
  def explode_daily_feed_filename(cls, filename):
    obj = {}

    num_dot_split = len(filename.split("."))
    if num_dot_split == 2:  # without extension
      name = filename
      ext = ""
    elif num_dot_split == 3:  # with extension
      name, ext = os.path.splitext(filename)
    else:
      raise Exception("Wrong filename:%s" % filename)

    split = name.split("--")
    items = split[0].split(".")
    if len(items) != 2:
      raise Exception("Wrong filename:%s" % filename)
    obj["item"] = items[0]
    obj["sub_item"] = items[1]
    obj["extension"] = ext.strip(".")  # It may be empty.
    if not obj["item"]:
      raise Exception("Empty item")
    if not obj["sub_item"]:
      raise Exception("Empty subitem")

    for elem in split[1:]:
      items = elem.split('-')
      if len(items) != 2:
        raise Exception("Wrong filename:%s" % filename)
      if items[0] in cls.keywords:
        raise Exception("Wrong filename:%s" % filename)
      obj[items[0]] = items[1]

    if not cls._check_dict(obj):
      raise Exception("Wrong filename:%s" % filename)
    return obj

  @classmethod
  def explode_daily_feed_file(cls, path):
    path = os.path.normpath(path)
    dirname_0, filename = os.path.split(path)
    dirname_1, table = os.path.split(dirname_0)
    dirname_2, data_set = os.path.split(dirname_1)
    dirname_3, date = os.path.split(dirname_2)
    daily_feed_root, symbol_set = os.path.split(dirname_3)

    if not filename:
      raise Exception("Empty filename")
    if not table:
      raise Exception("Empty table")
    if not data_set:
      raise Exception("Empty data set")
    if not date:
      raise Exception("Empty date")
    if not symbol_set:
      raise Exception("Empty symbol_set")
    if not daily_feed_root:
      raise Exception("Empty daily_feed_root")

    obj = cls.explode_daily_feed_filename(filename)
    obj["table"] = table
    obj["data_set"] = data_set
    obj["date"] = cls._try_to_convert_to_date(date)
    obj["symbol_set"] = symbol_set
    obj["daily_feed_root"] = daily_feed_root

    if not cls._check_dict(obj):
      raise Exception("Wrong filename:%s" % filename)
    return obj
